package com.kapcb.mvc;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.kapcb.common.constant.StringPool;
import com.kapcb.common.enums.ResultCodeEnum;
import com.kapcb.common.result.ResultMessage;
import com.kapcb.context.constant.ApplicationContextConstant;
import com.kapcb.mvc.advice.MvcResponseBodyAdvice;
import com.kapcb.mvc.advice.RestControllerAdvance;
import com.kapcb.mvc.filter.ApplicationThreadContextClearFilter;
import com.kapcb.mvc.listener.OncePerApplicationStartedEventListener;
import com.kapcb.mvc.listener.ReportServiceProxy;
import com.kapcb.mvc.listener.ServiceRunStatusListener;
import com.kapcb.mvc.listener.ServletRequestHandledEventListener;
import com.kapcb.mvc.properties.MvcProperties;
import com.kapcb.mvc.runner.BaseApplicationRunner;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.hibernate.validator.HibernateValidator;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * <a>Title: MvcAutoConfiguration </a>
 * <a>Author: kapcb <a>
 * <a>Description: MvcAutoConfiguration <a>
 *
 * @author Kpacb
 * @version 1.0
 * @date 2024/5/26 5:34
 * @since 1.0
 */
@Slf4j
@Configuration
@EnableConfigurationProperties({MvcProperties.class})
@ConditionalOnClass(RestController.class)
public class MvcAutoConfiguration {

    private final MvcProperties mvcProperties;

    public MvcAutoConfiguration(MvcProperties mvcProperties) {
        this.mvcProperties = mvcProperties;
    }

    /**
     * JSON 反序列化时自动将 Long 转化为 String 类型返回给前端，避免 Long 类型的精度丢失问题
     *
     * @param jackson2ObjectMapperBuilder Jackson2ObjectMapperBuilder
     * @return a {@link ObjectMapper} object
     */
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
        if (log.isDebugEnabled()) {
            log.debug("begin load ObjectMapper into IOC ...");
        }
        ObjectMapper objectMapper = jackson2ObjectMapperBuilder.createXmlMapper(false).build();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }

    /**
     * 默认线程上下文清理过滤器
     *
     * @return a {@link FilterRegistrationBean} object
     */
    @Bean
    public FilterRegistrationBean<ApplicationThreadContextClearFilter> applicationThreadContextClearFilter() {
        if (log.isDebugEnabled()) {
            log.debug("begin load applicationThreadContextClearFilter into IOC ......");
        }
        FilterRegistrationBean<ApplicationThreadContextClearFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new ApplicationThreadContextClearFilter());
        filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return filterRegistrationBean;
    }

    @Bean
    @ConditionalOnMissingBean(ReportServiceProxy.class)
    @ConditionalOnProperty(prefix = ApplicationContextConstant.REPORT_LISTENER, name = StringPool.ENABLE, havingValue = StringPool.TRUE)
    public ReportServiceProxy reportServiceProxy() {
        if (log.isDebugEnabled()) {
            log.debug("begin load ReportServiceProxy into IOC ...");
        }
        return new ReportServiceProxy();
    }

    @Bean
    @ConditionalOnMissingBean(ServiceRunStatusListener.class)
    @ConditionalOnProperty(prefix = ApplicationContextConstant.SERVER_RUN_STATUS_LISTENER, name = StringPool.ENABLE, havingValue = StringPool.TRUE)
    public ServiceRunStatusListener serviceRunStatusListener() {
        if (log.isDebugEnabled()) {
            log.debug("begin load ServiceRunStatusListener into IOC ...");
        }
        return new ServiceRunStatusListener();
    }

    @Bean
    @ConditionalOnMissingBean(ServiceRunStatusListener.class)
    @ConditionalOnProperty(prefix = ApplicationContextConstant.REQUEST_HANDLED_LISTENER, name = StringPool.ENABLE, havingValue = StringPool.TRUE)
    public ServletRequestHandledEventListener servletRequestHandleEventListener() {
        if (log.isDebugEnabled()) {
            log.debug("begin load ServletRequestHandleEventListener into IOC ...");
        }
        return new ServletRequestHandledEventListener();
    }

    @Bean
    @ConditionalOnMissingBean(OncePerApplicationStartedEventListener.class)
    @ConditionalOnProperty(prefix = ApplicationContextConstant.ONCE_PRE_APPLICATION_STARTED_LISTENER, name = StringPool.ENABLE, havingValue = StringPool.TRUE)
    public OncePerApplicationStartedEventListener oncePerApplicationStartedEventListener() {
        if (log.isDebugEnabled()) {
            log.debug("begin load OncePerApplicationStartedEventListener into IOC ...");
        }
        return new OncePerApplicationStartedEventListener();
    }

    /**
     * 设置 Hibernate Validator 为快速失败模式
     * 失败快速返回，只要有一个参数校验不通过，则返回
     *
     * @return LocalValidatorFactoryBean
     */
    @Bean
    @ConditionalOnMissingBean(value = LocalValidatorFactoryBean.class)
    public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
        if (log.isDebugEnabled()) {
            log.debug("begin load LocalValidatorFactoryBean into IOC ...");
        }
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();

        // 国际化
        factoryBean.setValidationMessageSource(messageSource);
        // 设置使用 HibernateValidator 校验器
        factoryBean.setProviderClass(HibernateValidator.class);

        Properties properties = new Properties();
        properties.setProperty("hibernate.validator.fail_fast", StringPool.TRUE);
        factoryBean.setValidationProperties(properties);
        // 加载配置
        factoryBean.afterPropertiesSet();
        return factoryBean;
    }

    @Bean("baseApplicationRunner")
    @ConditionalOnMissingBean(name = "baseApplicationRunner")
    public ApplicationRunner baseApplicationRunner() {
        if (log.isDebugEnabled()) {
            log.debug("begin load baseApplicationRunner into IOC ...");
        }
        return new BaseApplicationRunner();
    }

    @Bean
    @ConditionalOnMissingBean(value = ResponseBodyAdvice.class)
    public MvcResponseBodyAdvice mvcResponseBodyAdvice() {
        if (log.isDebugEnabled()) {
            log.debug("begin load mvcResponseHandler into IOC ...");
        }
        return new MvcResponseBodyAdvice();
    }

    @Bean
    @ConditionalOnMissingBean(value = RestControllerAdvance.class)
    public RestControllerAdvance restControllerAdvance() {
        if (log.isDebugEnabled()) {
            log.debug("begin load restControllerAdvance into IOC ...");
        }
        return new RestControllerAdvance();
    }

    /**
     * sa token 权限认证
     *
     * @return a {@link cn.dev33.satoken.filter.SaServletFilter} object.
     */
    @Bean
    @ConditionalOnMissingBean(value = SaServletFilter.class)
    public SaServletFilter saServletFilter() {
        if (log.isDebugEnabled()) {
            log.error("begin load saServletFilter into IOC ......");
        }

        SaServletFilter filter = new SaServletFilter();
        List<String> includeAuthUrlPrefix = mvcProperties.getIncludeAuthUrlPrefix();
        if (CollectionUtils.isNotEmpty(includeAuthUrlPrefix)) {
            filter.setIncludeList(includeAuthUrlPrefix);
        }

        List<String> excludeAuthUrlPrefix = mvcProperties.getExcludeAuthUrlPrefix();
        if (CollectionUtils.isNotEmpty(excludeAuthUrlPrefix)) {
            filter.setExcludeList(excludeAuthUrlPrefix);
        }

        filter.setAuth(auth -> {
            // 登录校验 -- 拦截所有路由
            SaRouter.match(includeAuthUrlPrefix)
                    .notMatch(excludeAuthUrlPrefix)
                    .check(r -> {
                        // 检查是否登录 是否有token
                        StpUtil.checkLogin();
                    });
        });

        filter.setError(exception -> {
            if (exception instanceof NotLoginException) {
                return ResultMessage.failure(ResultCodeEnum.UNAUTHORIZED);
            }
            return ResultMessage.failure(ResultCodeEnum.UNAUTHORIZED.code(), "认证失败，无法访问系统资源");
        });

        return filter;
    }

    @Bean("corsFilter")
    @ConditionalOnMissingBean(value = CorsFilter.class)
    public CorsFilter corsFilter() {
        if (log.isDebugEnabled()) {
            log.error("begin load corsFilter into IOC ......");
        }

        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // sessionId 多次访问一致
        corsConfiguration.setAllowCredentials(true);
        // 允许访问的客户端域名
        List<String> allowedOriginPatterns = new ArrayList<>();
        allowedOriginPatterns.add(StringPool.STAR);
        corsConfiguration.setAllowedOriginPatterns(allowedOriginPatterns);
        // 允许任何头
        corsConfiguration.addAllowedHeader(StringPool.STAR);
        // 允许任何方法（post、get等）
        corsConfiguration.addAllowedMethod(StringPool.STAR);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 对接口配置跨域设置
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }
}
